import set from 'lodash/set';
import gqcPermissionCheck from '@kuiper/auth/dist/server/lib/gqcPermissionCheck';
import nestedToOneLevelPath from '@kuiper-helpers/nested-to-one-level-path';
import errorCodesBuilder from '../../helpers/errorCodesBuilder';
import updateOne from './updateOne';
import escapeRegExp from '../../helpers/escapeRegExp';

const errorCodes = errorCodesBuilder('');

export default server => {
	const {
		app: {
			schemaComposer,
			model: { Asset },
		},
	} = server;

	const tc = server.methods.registerGraphQLSchema({
		name: 'Asset',
		model: Asset,
		queryFields: {
			One: 'findOne',
			Many: 'findMany',
			Pagination: 'pagination',
			Count: 'count',
		},
		mutationFields: {},
		onPreAddFields(tc) {
			const {
				app: {
					graphqlResolvers,
					schemaComposer,
					schema: { Task, User, Job },
				},
			} = server;

			schemaComposer.createObjectTC({
				name: 'AssetStatsPayload',
				fields: {
					subdomainCount: 'Int',
					ipCount: 'Int',
					portCount: 'Int',
					ipBindDomainCount: 'Int',
					addSubdomainCount: 'Int',
					addIpCount: 'Int',
					addPortCount: 'Int',
					addIpBindDomainCount: 'Int',
					removeSubdomainCount: 'Int',
					removeIpCount: 'Int',
					removePortCount: 'Int',
					removeIpBindDomainCount: 'Int',
					changePortServiceCount: 'Int',
				},
			});

			tc.addRelation('task', {
				resolver: () => Task.mongooseResolvers.dataLoader({ lean: true }),
				prepareArgs: {
					_id: source => source.taskId,
				},
				projection: { taskId: 1 },
			});
			tc.addRelation('result', {
				resolver: () => Task.mongooseResolvers.dataLoader({ lean: true }),
				prepareArgs: {
					_id: source => source.resultId,
				},
				projection: { resultId: 1 },
			});
			tc.addRelation('job', {
				resolver: () => Job.mongooseResolvers.dataLoader({ lean: true }),
				prepareArgs: {
					_id: source => source.jobId,
				},
				projection: { jobId: 1 },
			});
			tc.addRelation('prevResult', {
				resolver: () => Task.mongooseResolvers.dataLoader({ lean: true }),
				prepareArgs: {
					_id: source => source.prevResultId,
				},
				projection: { prevResultId: 1 },
			});
			tc.addRelation('user', {
				resolver: () => User.mongooseResolvers.dataLoader({ lean: true }),
				prepareArgs: {
					_id: source => source.uid,
				},
				projection: { uid: 1 },
			});

			const permissionCheck = gqcPermissionCheck({
				required: 'cloud-monitor\x01listAsset',
				selfOnlyArgsBuilder(
					_source,
					args,
					{
						request: {
							auth: {
								credentials: { uid },
							},
						},
					}
				) {
					set(args, ['filter', 'uid'], uid);
				},
			});

			graphqlResolvers.Query.AssetOne = graphqlResolvers.Query.AssetOne.withMiddlewares(
				[permissionCheck]
			);

			graphqlResolvers.Query.AssetMany = graphqlResolvers.Query.AssetMany.addFilterArg(
				{
					name: 'taskSettings',
					type: 'AssetTaskSettingsInput',
					description: '任务配置',
					query: (query, value) => {
						Object.assign(query, nestedToOneLevelPath(value, 'taskSettings'));
					},
				}
			)
				.addFilterArg({
					name: 'alertSettings',
					type: 'AssetAlertSettingsInput',
					description: '告警配置',
					query: (query, value) => {
						Object.assign(query, nestedToOneLevelPath(value, 'alertSettings'));
					},
				})
				.withMiddlewares([permissionCheck]);

			graphqlResolvers.Query.AssetCount = graphqlResolvers.Query.AssetCount.addFilterArg(
				{
					name: 'taskSettings',
					type: 'AssetTaskSettingsInput',
					description: '任务配置',
					query: (query, value) => {
						Object.assign(query, nestedToOneLevelPath(value, 'taskSettings'));
					},
				}
			).withMiddlewares([permissionCheck]);

			graphqlResolvers.Query.AssetPagination = graphqlResolvers.Query.AssetPagination.addFilterArg(
				{
					name: 'taskSettings',
					type: 'AssetTaskSettingsInput',
					description: '任务配置',
					query: (query, value) => {
						Object.assign(query, nestedToOneLevelPath(value, 'taskSettings'));
					},
				}
			)
				.addFilterArg({
					name: 'targetSearch',
					type: 'String',
					description: '域名搜索',
					query: (query, value) => {
						query.target = {
							$regex: escapeRegExp(value.trim()),
							$options: 'i',
						};
					},
				})
				.addFilterArg({
					name: 'alertSettings',
					type: 'AssetAlertSettingsInput',
					description: '告警配置',
					query: (query, value) => {
						Object.assign(query, nestedToOneLevelPath(value, 'alertSettings'));
					},
				})
				.addFilterArg({
					name: 'createTimeGTE',
					type: 'Date',
					description: '开始时间大于等于',
					query: (query, value) => {
						const fieldName = 'createTime';
						let field = query[fieldName] || {};
						if (typeof field !== 'object') field = {};
						query[fieldName] = { ...field, $gte: value };
					},
				})
				.addFilterArg({
					name: 'createTimeLTE',
					type: 'Date',
					description: '开始时间小于等于',
					query: (query, value) => {
						const fieldName = 'createTime';
						let field = query[fieldName] || {};
						if (typeof field !== 'object') field = {};
						query[fieldName] = { ...field, $lte: value };
					},
				})
				.withMiddlewares([permissionCheck]);
		},
	});

	tc.mongooseResolvers.updateOne();
	schemaComposer.Mutation.addFields({
		AssetUpdateOne: updateOne,
		assetAddSubdomain: {
			description: '资产变动添加自定义子域名',
			type: 'Asset',
			args: {
				assetId: 'MongoID!',
				domain: 'String!',
				ip: 'String',
				note: 'String',
			},
			async resolve(root, { assetId, domain, ip, note }, { request }) {
				const {
					server: {
						app: {
							model: { Asset },
						},
					},
				} = request;

				let asset;
				try {
					asset = await Asset.findOne({ _id: assetId });
					if (!asset) throw new Error('没有找到对应的Mongo记录');
				} catch (err) {
					const error = errorCodes['[01]停止任务：删除任务失败'].from(err);
					server.log.error({ error }, error.rawMessage);
					throw error;
				}

				asset.customSubdomains = asset.customSubdomains.filter(
					x => x.domain !== domain
				);
				asset.customSubdomains.push({ domain, ip, note });

				try {
					await asset.save();
				} catch (err) {
					const error = errorCodes[
						'[01]添加资产变动自定义子域保存资产失败'
					].from(err);
					request.log.error({ error }, error.rawMessage);
					throw error;
				}

				request.log.audit(
					{
						affects: [asset.target],
						module: 'asset',
						actionType: 'create',
						actionMessage: '添加资产变动自定义子域',
						detail: `添加${asset.target}资产变动自定义子域 ${asset.target}`,
					},
					`添加资产变动自定义子域 ${asset.target}`
				);

				return asset;
			},
		},
		addManualAssetTask: {
			description: '添加自定义资产扫描任务',
			type: 'JSON',
			args: {
				uid: 'MongoID',
				alert: 'JSON!',
				collect: 'JSON!',
				targetId: 'MongoID!',
			},
			async resolve(
				root,
				{ uid: customUid, alert, collect, targetId },
				{ request }
			) {
				const {
					headers: { cookie },
					server: {
						methods: { createOrUpdateTask, ensureBalance, calcBalance },
						app: {
							model: { Asset },
						},
					},
					auth: {
						credentials: { uid: currentUid },
					},
				} = request;
				const uid = customUid || currentUid;

				let asset;
				try {
					asset = await Asset.findOne({
						uid,
						_id: targetId,
					}).populate('jobId');
					if (!asset) throw new Error('没有找到关联的Mongo记录');
				} catch (err) {
					const error = errorCodes[
						'[02]自定义扫描任务：查询关联资产mongo记录失败'
					].from(err);
					server.log.error({ error, uid, targetId }, error.rawMessage);
					throw error;
				}

				const balanceAction = {
					uid,
					ops: [{ key: 'manualTask', value: -1 }],
				};

				await ensureBalance(balanceAction);

				try {
					await createOrUpdateTask({
						cookie,
						taskType: 'asset',
						triggerType: 'manual',
						body: {
							taskSettings: {
								alert,
								collect,
							},
							jobId: asset?.jobId?._id,
							enableMonitor: true,
							immediateExec: true,
						},
					});
				} catch (err) {
					const error = errorCodes['[02]自定义扫描任务：创建任务失败'].from(
						err
					);
					server.log.error({ error }, error.rawMessage);
					throw error;
				}

				await calcBalance(balanceAction);

				request.log.audit(
					{
						affects: [asset.target],
						modele: 'asset',
						actionType: 'create',
						actionMessage: '下发监测任务',
						detail: `下发资产变动自定义监测任务，资产名称：${
							asset?.jobId?.note
						}，资产地址：${asset.target}，监测内容：子域名变动、端口变动，${
							alert?.enable ? '' : '未'
						}开启告警通知。`,
					},
					`添加自定义资产变动扫描任务 ${asset.target}`
				);

				return {};
			},
		},
	});
};
